#include "JSGlue.h"

using namespace v8;

#define DECLARE(x) \
	Handle<FunctionTemplate> 	JSGlue::x::templ; \
	Handle<ObjectTemplate> 		JSGlue::x::proto; \
	Handle<ObjectTemplate> 		JSGlue::x::inst;

#define METHODS(x) const JSGlue::js_reg JSGlue::x::methods[] = {
#define END	{0,0}};

//{{{ Vector3
DECLARE(Vector3)
METHODS(Vector3)
{"toString", JSGlue::Vector3::toString},
{"length", JSGlue::Vector3::length},
{"add", JSGlue::Vector3::add},
{"mul", JSGlue::Vector3::mul},
END
//}}}

//{{{ Object
DECLARE(Object)
//}}}

//{{{ GameObject
DECLARE(GameObject)
//}}}


//{{{ js_do_file
/**
 * \brief Execute a javascript file
 * \param context_scope
 * \param name Filename
 */
void js_do_file(v8::Context::Scope context_scope, const char* name)
{
	FILE* file = fopen(name, "rb");
	if (file == NULL) return;
	v8::HandleScope handle_scope;

	fseek(file, 0, SEEK_END);
	int size = ftell(file);
	rewind(file);

	char* chars = new char[size + 1];
	chars[size] = '\0';
	for (int i = 0; i < size;)
		{
			int read = fread(&chars[i], 1, size - i, file);
			i += read;
		}
	fclose(file);
	v8::Handle<v8::String> source = v8::String::New(chars, size);
	delete[] chars;

	v8::TryCatch try_catch;
	v8::Handle<v8::Script> script = v8::Script::Compile(source, NULL, NULL);
	if (script.IsEmpty())
		{
			return;
		}
	else
		{
			v8::Handle<v8::Value> result = script->Run();
			if (result.IsEmpty())
				{
					v8::Handle<v8::Value> exception = try_catch.Exception();
					v8::String::AsciiValue exception_str(exception);
					printf("%s\n", *exception_str);
				}
			else
				{
					if (!result->IsUndefined())
						{
							v8::String::AsciiValue asc(result);
							const char* cstr = *asc;
							printf("%s\n", cstr);
						}
				}
		}
	return;
}
//}}}

// Convert a V8 String to a std::string.
std::string V8StringToStdString(v8::Handle<v8::String> s)
{
	int len = s->Utf8Length();
//  int len = s->Length();
	std::string result;
	char* buffer = new char[len];
	s->WriteUtf8(buffer, len);
//  s->WriteAscii(buffer, len);
	return result;
}

// Convert a std::string (UTF8) to a V8 string.
v8::Local<v8::String> StdStringToV8String(const std::string& s)
{
	return v8::String::New(s.data(), s.size());
}

// String-ize a V8 object by calling its toString() method. Returns true
// on success. This may fail if the toString() throws an exception.
bool V8ObjectToString(v8::Handle<v8::Value> object, std::string* result)
{
	if (object.IsEmpty())
		return false;

	v8::HandleScope scope;
	v8::Local<v8::String> str_object = object->ToString();
	if (str_object.IsEmpty())
		return false;
	*result = V8StringToStdString(str_object);
	return true;
}




Handle<v8::Value> toV8(ValueType type, void* ptr)
{
	switch(type)
		{
		case BOOL:
			return v8::Boolean::New(*(bool*)ptr);
			break;
		case STRING:
			return v8::String::New((const char*)ptr);
			break;
		case FLOAT:
			return v8::Number::New(*(float*)ptr);
			break;
		case VECTOR3:
			//return JSGlue::wrap<Ogre::Vector3>(ptr);
			return JSGlue::Vector3::wrap(ptr);
			break;
		case OBJECT:
			return JSGlue::Object::wrap(ptr);
			break;
		case UNKNOWN:
		default:
			break;
		}

	return Handle<v8::Value>();
}

namespace JSGlue
{

	void glue(Persistent<Context> ctx)
	{
		//		v8Wrapper<Vector3>::glue(ctx);

		Vector3::glue(ctx);

		Object::glue(ctx);
		GameObject::glue(ctx);
	}


	//{{{ Vector3
	void Vector3::gc(Persistent<v8::Object> self, void* parameter)
	{
		Local<External> external = Local<External>::Cast(self->GetInternalField(0));
		delete static_cast<Ogre::Vector3*>(external->Value());
	}


	void Vector3::glue(Persistent<Context> ctx)
	{
		HandleScope scope;

		templ = FunctionTemplate::New(create);
		templ->SetClassName(v8::String::New("Vector3"));
		templ->Set("add", FunctionTemplate::New(addStatic));

		inst = templ->InstanceTemplate();
		inst->SetInternalFieldCount(1);

		inst->SetAccessor(v8::String::New("x"), Get, Set);
		inst->SetAccessor(v8::String::New("y"), Get, Set);
		inst->SetAccessor(v8::String::New("z"), Get, Set);

		int i = 0;
		JSGlue::js_reg f = methods[i];
		while(f.name && f.func)
			{
				inst->Set(f.name, FunctionTemplate::New(f.func));
				f = methods[i++];
			}

		proto = templ->PrototypeTemplate();
//			proto->Set("copy", FunctionTemplate::New(copy));
//			proto->Set("length", FunctionTemplate::New(length));
//			proto->Set("toString", FunctionTemplate::New(toString));
//			proto->Set("add", FunctionTemplate::New(add));
//			proto->Set("mul", FunctionTemplate::New(mul));

		ctx->Enter();
		ctx->Global()->Set( v8::String::NewSymbol( "Vector3" ), templ->GetFunction() );
	}


	v8::Handle<v8::Value> Vector3::create(const v8::Arguments &args)
	{
		HandleScope scope;

		if (!args.IsConstructCall()) return ThrowException(v8::String::New("Cannot call constructor as function"));

		if (args.Length() != 0 && args.Length() != 3) return ThrowException(v8::String::New("Expected either zero or three arguments"));

		Ogre::Vector3 *v;
		double x,y,z;

		if(args.Length() == 3)
			{
				x = args[0]->NumberValue();
				y = args[1]->NumberValue();
				z = args[2]->NumberValue();

				v = new Ogre::Vector3(x,y,z);
			}
		else
			v = new Ogre::Vector3(Ogre::Vector3::ZERO);

		v8::Persistent<v8::Object> self = Persistent<v8::Object>::New(args.Holder());
		self->SetInternalField(0, External::New(v));
		return self;

//			return wrap(v);
	}

	Ogre::Vector3* Vector3::unwrap(v8::Handle<v8::Object> obj)
	{
		v8::Handle<v8::External> field;
		field = v8::Handle<v8::External>::Cast(obj->GetInternalField(0));
		void* ptr = field->Value();
		return static_cast<Ogre::Vector3*>(ptr);
	}

	Handle<v8::Object> Vector3::wrap(void *v)
	{
		v8::HandleScope handle_scope;

		Handle<ObjectTemplate> objTempl = ObjectTemplate::New();
		objTempl->SetInternalFieldCount(1);

		int i = 0;
		JSGlue::js_reg f = methods[i];
		while(f.name && f.func)
			{
				objTempl->Set(f.name, FunctionTemplate::New(f.func));
				f = methods[i++];
			}


		Handle<v8::Object> result = objTempl->NewInstance();
		Handle<External> ptr = External::New(static_cast<Ogre::Vector3*>(v));
		result->SetInternalField(0, ptr);

		return handle_scope.Close(result);
	}

	Handle<v8::Value> Vector3::toString(const Arguments& args)
	{
		Ogre::Vector3* v = unwrap(args.Holder());
		std::stringstream ss;
		ss << *v;
		return v8::String::New(ss.str().c_str());
	}

	Handle<v8::Value> Vector3::length(const Arguments& args)
	{
		Local<v8::Object> self = args.Holder();
		Local<External> wrap = Local<External>::Cast(self->GetInternalField(0));
		void* ptr = wrap->Value();
		return Number::New(static_cast<Ogre::Vector3*>(ptr)->length());
	}

	Handle<v8::Value> Vector3::copy(const Arguments& args)
	{
		Local<v8::Object> self = args.Holder();
		Local<External> wrap = Local<External>::Cast(self->GetInternalField(0));
//			void* ptr = wrap->Value();
//			return v8::Object::New(static_cast<Ogre::Vector3*>(ptr));
//			Ogre::Vector3* v = static_cast<Ogre::Vector3*>(ptr);
		return self->Clone();
		return v8::Undefined();
	}

	Handle<v8::Value> Vector3::add(const Arguments& args)
	{
		Local<v8::Object> self = args.Holder();
		Local<External> external = Local<External>::Cast(self->GetInternalField(0));
		Ogre::Vector3* v = static_cast<Ogre::Vector3*>(external->Value());

		Ogre::Vector3* u;
		if(args[0]->IsObject())
			{
				// && args[0]->ToObject()->InternalFieldCount()==2)
				v8::Local<v8::External> ext = v8::Local<v8::External>::Cast(args[0]->ToObject()->GetInternalField(0));
//					if (*v8::String::Utf8Value(o.GetInternalField(1)->ToString())==std::string("Vector3"))
				u = static_cast<Ogre::Vector3*>(ext->Value());
			}

		std::cout << *v << std::endl;
		std::cout << *u << std::endl;

		*v = Ogre::Vector3(*v) + Ogre::Vector3(*u);

		self->SetInternalField(0, External::New(v));
		return self;
	}

	Handle<v8::Value> Vector3::mul(const Arguments& args)
	{
		if (args.Length() != 1) return ThrowException(v8::String::New("Single argument expected"));
		if(!args[0]->IsNumber()) return ThrowException(v8::String::New("Number expected"));

		Local<v8::Object> self = args.Holder();
		Local<External> external = Local<External>::Cast(self->GetInternalField(0));
		Ogre::Vector3* v = static_cast<Ogre::Vector3*>(external->Value());

		double m = args[0]->NumberValue();
		*v = (*v) * m;
		self->SetInternalField(0, External::New(v));
		return self;
	}
	//}}}

	//{{{ Object
	void Object::glue(Persistent<Context> ctx)
	{
		HandleScope scope;

		templ = FunctionTemplate::New();
		//templ = FunctionTemplate::New(create);
		templ->SetClassName(v8::String::New("Object"));

		inst = templ->InstanceTemplate();
		inst->SetInternalFieldCount(1);

		inst->SetNamedPropertyHandler(MapGet, MapSet);

		proto = templ->PrototypeTemplate();
	}

	::Object* Object::unwrap(v8::Handle<v8::Object> obj)
	{
		v8::Handle<v8::External> field;
		field = v8::Handle<v8::External>::Cast(obj->GetInternalField(0));
		void* ptr = field->Value();
		return static_cast< ::Object*>(ptr);
	}

	Handle<v8::Object> Object::wrap(void *v)
	{
		v8::HandleScope handle_scope;

		Handle<ObjectTemplate> objTempl = ObjectTemplate::New();
		objTempl->SetInternalFieldCount(1);

		objTempl->SetNamedPropertyHandler(MapGet, MapSet);

		Handle<v8::Object> result = objTempl->NewInstance();
		Handle<External> ptr = External::New(static_cast< ::Object*>(v));
		result->SetInternalField(0, ptr);

		return handle_scope.Close(result);
	}



	Handle<v8::Value> Object::MapGet(Local<v8::String> name, const AccessorInfo &info)
	{
		::Object* o = unwrap(info.Holder());

		v8::String::AsciiValue asc(name);

		if(!o->hasProperty(*asc)) return Handle<v8::Value>();
		IProperty* p = o->getIProperty(*asc);

		return toV8(TypeInfo::getType(p->getTypeName()), p->_getValuePtr());
	}

	Handle<v8::Value> Object::MapSet(Local<v8::String> name, Local<v8::Value> value, const AccessorInfo& info)
	{
		::Object *o = unwrap(info.Holder());

		v8::String::AsciiValue asc(name);
		std::cout << *asc << std::endl;

		// oh man... i just hate doing this...

		if(o->hasProperty(*asc))
			{
				IProperty* p = o->getIProperty(*asc);

				if(value->IsBoolean())
					{
						v8::Local<v8::Boolean>  v8bool = value->ToBoolean();
						prop_cast<bool>(p)->Set(v8bool->Value());
					}
				else if(value->IsNumber())
					{
						v8::Local<v8::Number>  v8num = value->ToNumber();
						prop_cast<float>(p)->Set(v8num->Value());
					}
				else if(value->IsString())
					{
//						v8::Local<v8::String>  v8str = value->ToString();
//						prop_cast<const char*>(p)->Set(*(v8::String::Utf8Value(value)));
//						prop_cast<const char*>(p)->Set(*(v8::String::Utf8Value(value->ToString())));
						v8::String::AsciiValue v8asc(value);
						std::cout << (const char*)(*v8asc) << std::endl;
					
						prop_cast<const char*>(p)->Set(*(v8::String::AsciiValue(value->ToString())));
					}
				else if(value->IsObject())
				{
						v8::String::AsciiValue v8asc(value->ToString());
						std::cout << (const char*)(*v8asc) << std::endl;
					
				}
				else if(value->IsFunction())
				{	
					v8::Function* f = v8::Function::Cast(*value);
//					return Handle<Value>(f);
//					v8::Local<v8::Function>  v8f = value->ToFunction();

				}
				else
					{
						std::string typeName = p->getTypeName();
						switch(TypeInfo::getType(typeName))
							{
							case VECTOR3:
							{
								Handle<v8::Object> o = value->ToObject();
								Ogre::Vector3* v = JSGlue::Vector3::unwrap(o);
								prop_cast<Ogre::Vector3>(p)->Set(*v);

							}
							break;
							}

					}
			}
		else
			{
				if(value->IsBoolean())
					{
						v8::Local<v8::Boolean>  v8bool = value->ToBoolean();
						o->addProperty<bool>(*asc, v8bool->Value());
					}
				else if(value->IsNumber())
					{
						v8::Local<v8::Number>  v8num = value->ToNumber();
						o->addProperty<float>(*asc, v8num->Value());
					}
				else if(value->IsString())
					{
						v8::Local<v8::String>  v8str = value->ToString();
						v8::String::AsciiValue v8asc(v8str);
						v8::String::AsciiValue v8asc2(value);
						
//						o->addProperty<const char*>(*asc, *v8asc);
						o->addProperty<const char*>(*asc, "UNIMPLEMENTED");					
					}


				return value;
			}

		return value;
	}
	//}}}


	//{{{ GameObject

	Handle<v8::Value> GameObject::create(const Arguments &args)
	{
		if (!args.IsConstructCall()) return ThrowException(v8::String::New("Cannot call constructor as function"));

		HandleScope scope;
		Handle<v8::Value> external;

		::GameObject *go = NULL;

		if (args[0]->IsExternal())
			{
				external = Handle<External>::Cast(args[0]);
			}
		else
			{
				go = new ::GameObject();
				external = External::New(go);
			}
		args.This()->SetInternalField(0, external);
		//return args.This();
		return scope.Close(args.This());
	}



	::GameObject* GameObject::unwrap(v8::Handle<v8::Object> obj)
	{
		v8::Handle<v8::External> field;
		field = v8::Handle<v8::External>::Cast(obj->GetInternalField(0));
		void* ptr = field->Value();
		return static_cast< ::GameObject*>(ptr);
	}

	Handle<v8::Object> GameObject::wrap(::GameObject *go)
	{
		v8::HandleScope handle_scope;

		Handle<ObjectTemplate> objTempl = ObjectTemplate::New();
		objTempl->SetInternalFieldCount(1);

		objTempl->SetNamedPropertyHandler(MapGet, MapSet);

		Handle<v8::Object> result = objTempl->NewInstance();
		Handle<External> ptr = External::New(go);
		result->SetInternalField(0, ptr);

		return handle_scope.Close(result);
	}

	Handle<v8::Value> GameObject::getByName(const Arguments& args)
	{
		HandleScope handle_scope;

		if (args.Length() != 1) return ThrowException(v8::String::New("Single argument expected"));
		if(!args[0]->IsString()) return ThrowException(v8::String::New("String expected"));

		Local<v8::String> name = args[0]->ToString();
		v8::String::AsciiValue asc(name);

		::GameObject* go = ::GameObject::mObjectsByName[*asc];

		if(go==NULL || strcmp(go->getName().c_str(), *asc)!=0)
			{
				std::stringstream ss;
				ss << "GameObject named '" << *asc << "' not found";
				return ThrowException(v8::String::New(ss.str().c_str()));
			}


		return handle_scope.Close(wrap(go));
		//return handle_scope.Close(wrap(new ::GameObject()));

	}

	void GameObject::gc(Persistent<v8::Object> self, void* parameter)
	{
//			Local<External> external = Local<External>::Cast(self->GetInternalField(0));
//			delete static_cast<::GameObject*>(external->Value());
	}


	void GameObject::glue(Persistent<Context> ctx)
	{
		HandleScope scope;

//			templ = FunctionTemplate::New();
		templ = FunctionTemplate::New(create);
		templ->SetClassName(v8::String::New("GameObject"));
		templ->Set("getByName", FunctionTemplate::New(getByName));

		inst = templ->InstanceTemplate();
		inst->SetInternalFieldCount(1);

		//inst->SetAccessor(v8::String::New("name"), Get, Set);
		inst->SetNamedPropertyHandler(MapGet, MapSet);

		proto = templ->PrototypeTemplate();
		proto->Set("toString", FunctionTemplate::New(toString));

		//TODO: "static" handler ?
		//proto->SetNamedPropertyHandler(MapGet, MapSet);

		ctx->Enter();
		ctx->Global()->Set( v8::String::NewSymbol( "GameObject" ), templ->GetFunction() );
	}

	Handle<v8::Value> GameObject::MapGet(Local<v8::String> name, const AccessorInfo &info)
	{
		::GameObject* go = unwrap(info.Holder());

		v8::String::AsciiValue asc(name);

//          std::cout << *go << "::" << *asc << std::endl;


		if(!go->hasProperty(*asc)) return Handle<v8::Value>();
		IProperty* p = go->getIProperty(*asc);

		return toV8(TypeInfo::getType(p->getTypeName()), p->_getValuePtr());

//		return Object::MapGet(name, info);

		return Handle<v8::Value>();
	}

	Handle<v8::Value> GameObject::MapSet(Local<v8::String> name, Local<v8::Value> value, const AccessorInfo& info)
	{
		::GameObject *go = unwrap(info.Holder());

//            std::cout << *go << std::endl;
		v8::String::AsciiValue asc(name);
		std::cout << *asc << std::endl;

		if(go->hasProperty(*asc))
			{
				IProperty* p = go->getIProperty(*asc);
				std::string typeName = p->getTypeName();

				if(value->IsBoolean())
					{
						v8::Local<v8::Boolean>  v8bool = value->ToBoolean();
						prop_cast<bool>(p)->Set(v8bool->Value());
					}
				else if(value->IsNumber())
					{
						v8::Local<v8::Number>  v8num = value->ToNumber();
					}
				else if(value->IsString())
					{
						v8::Local<v8::String>  v8str = value->ToString();
					}
				else
					{
						std::cout << typeName << std::endl;
						return value;
					}
			}
		else
			{

				// oh man...
				if(value->IsBoolean())
					{
						go->addProperty<bool>(*asc, value->ToBoolean()->Value());
					}
				else if(value->IsNumber())
					{
						go->addProperty<float>(*asc, value->ToNumber()->Value());
					}
				else if(value->IsString())
					{
						v8::Local<v8::String>  v8str = value->ToString();
						v8::String::AsciiValue v8val(v8str);
						go->addProperty<const char*>(*asc, *v8val);
					}
			}



		return value;
	}

	Handle<v8::Value> GameObject::toString(const Arguments& args)
	{
		::GameObject* go = unwrap(args.Holder());

		std::stringstream ss;
		ss << "GameObject(" << go->getName() << ")";

		return v8::String::New(ss.str().c_str());
	}
	//}}}

}
